Day 3 的 CLAUDE.md 裡,有一條寫得很強硬的規則:JavaScriptCore 只在 App 執行,絕不進 Widget。 這條規則來自 IMS 開發時差點踩下去的一個坑。
IMS 是研討會工作人員查任務用的 App,資料來自網站的 schedule.js,以及一份 22 KB 的 corrections.js,用來修正人名、補上遺漏的任務。我決定讓 App 用 JavaScriptCore 執行這兩個檔案,沿用網站的資料處理邏輯。這樣網站更新排班時,App 就能讀取更新,不必為了資料變動重新發版。
接著,我叫 AI 做 Widget:「顯示當下任務和下一場任務。」它直接沿用 App 的做法,在 Widget extension 裡建立 JSContext,執行同樣的兩個檔案。
單看資料處理邏輯,這個做法很合理。但 Widget extension 的記憶體預算和 App 不同,把 JavaScriptCore 一起搬進去,就有超出上限、被系統終止的風險。使用者看到的,可能只是 Widget 顯示不出來。
問題出在我只說了「要顯示什麼」,沒有交代「必須在什麼限制下完成」。最後,IMS 把分工定下來:App 負責解析資料,將 JSON 快照寫進 App Group;Widget 只讀快照,不執行 JavaScript。
「做什麼」只是需求的一部分。AI 還需要知道,這件事是在什麼環境裡做的。
這就是本篇要談的「情境工程」:整理 AI 完成任務所需的背景、限制與參考資料,讓它有依據地做決定。
當 AI 做出「看起來合理,放進專案卻不對」的東西時,可以先檢查是不是少給了資訊。你沒交代的地方,它可能自行補上一個假設,而那個假設未必適合你的專案。
我會把這些資訊分成四層:
| 層次 | 要回答的問題 | 缺少時容易出現的問題 | 適合放在哪裡 |
|---|---|---|---|
| 任務層 | 這次要完成什麼?怎樣算做完? | 多做沒要求的功能,或漏掉驗收條件 | prompt、ticket |
| 領域層 | 平台或框架有哪些慣例與限制? | 套用不適合這個平台的做法 | CLAUDE.md、Skill |
| 系統層 | 這個專案有哪些既有設計、依賴與不可任意更動的規則? | 重複實作,或破壞既有設計 | CLAUDE.md、相關程式碼 |
| 使用者層 | 誰會用?在哪裡用?最在乎什麼? | 功能正確,實際操作卻不方便 | 設計文件、實作計畫 |
領域層和系統層有時會連在一起。以剛才的 Widget 為例,「Widget extension 有記憶體限制」是平台知識,屬於領域層;「IMS 由 App 解析、Widget 只讀快照」則是這個專案的設計決策,屬於系統層。分類的目的是幫你找出缺口,不必硬把每條資訊只放進一格。
使用者層則會影響另一種決定。IMS 的使用者是在會場跑來跑去的工作人員,手機平常放在口袋裡,掏出來看一眼,就要知道「現在該去哪」。這個情境,才是 Widget 要大字、Live Activity 要倒數、通知要提前 10 分鐘的理由。
如果只說「把任務資訊都顯示出來」,AI 可能做出資訊完整、卻要滑好幾頁才能找到重點的畫面。
知道要補哪四層之後,下一個問題是:要把所有資料都貼進 prompt 嗎?
不必。AI 一次能處理的上下文有限,塞進太多不相關的內容,反而容易讓關鍵限制埋在細節裡。直接貼程式碼還有一個問題:專案已經改了幾輪,對話裡留著的卻可能是舊版。
我通常這樣分:
「提供位置」也要說明為什麼要讀,像這樣:
資料載入邏輯在
IMS/Services/ScheduleService.swift;資料來源的備援順序,請看SharedStore.swift裡的ScheduleFileKind。
這樣 AI 知道從哪裡找,也知道要找什麼。有讀取專案的工具時,它就能查看工作目錄中的檔案,不必依賴你先前貼過的版本。
如果任務是 code review,還要再交代審查範圍與版本。例如,用 git diff main...HEAD 指定比較範圍;需要讓後續複審對照同一份內容時,則記錄對應的 commit hash。分支名稱會隨提交移動,不能單靠名稱固定版本。
重點是讓雙方清楚:這次要讀哪裡,以及要以哪個版本為準。
拿昨天寫好的 CLAUDE.md,挑一個小任務,試著把四層資訊補齊。不必全部重寫進 prompt;已經有的規則,確認它寫清楚了就好。
第一步:檢查專案裡已經有哪些背景資訊。
activatedPerson 為唯一依據」。第二步:補上本次任務與驗收條件。
例如,在 IMS 的下一場任務卡片上,加上「距離開始還有多久」的文字。把相關資訊整理在一起,就是一份四層 prompt:
## 任務
在 TaskTimelineView 的下一場任務卡片上,顯示距離開始還有多久。
驗收:不足 60 分鐘顯示「還有 N 分鐘」;
60 分鐘以上顯示「還有 N 小時 M 分鐘」;任務已開始則不顯示。
## 領域
SwiftUI、iOS 17。時間顯示需考慮多語系,優先使用 Foundation 的格式化功能。
選用的格式必須符合上面的驗收條件。
## 系統
現有的時間計算在 Shared/MissionTimeline.swift,先讀取並沿用適用的函式。
任務 block 的定義見 Shared/TaskBlock.swift。
測試放 IMSTests/MissionTimelineTests.swift,先寫會失敗的測試。
## 使用者
會場工作人員,手機平常放在口袋,掏出來看一眼,三秒內要懂。
文字要容易辨識,時間單位不要縮寫。
第三步:從修改結果確認,這些資訊有沒有發揮作用。
看 diff 時,除了功能有沒有做出來,也檢查:它有沒有沿用 MissionTimeline 的既有邏輯?有沒有先寫測試?字級和措辭是否符合「三秒內看懂」的使用情境?
如果某一點沒做到,回頭看對應那一層:是資訊沒給清楚,還是 AI 沒有遵守?四層模板也能幫你定位問題。
只給任務,讓 AI 猜其他三層。 「幫我做 X」說清楚了功能,卻沒交代平台限制、既有設計與使用情境。AI 就算把功能做出來,也可能不適合放進你的專案。
把整個檔案貼進去,後續卻沒更新。 第三輪修改還參考第一輪的程式碼,容易產出與現況衝突的改動。長篇資料改用路徑提供,並交代需要參考的版本。
只說「你自己看 codebase」。 AI 可以讀到程式目前怎麼運作,卻未必能知道當初為什麼這樣設計,以及哪些決策不能任意改。這些理由需要明確寫下來。
漏掉使用者。 字級、資訊密度、措辭都會影響使用體驗。如果任務涉及介面,使用情境就是 AI 做這些決定時需要的依據。
把下面的模板存成 prompt-四層模板.md,下次派任務前填一次。填不出來的地方,就是需要再釐清或查資料的地方。
## 任務
<要做什麼,一到三句>
驗收:<怎樣算做完,要能驗證>
## 領域
<平台/框架限制、慣例、要用或不要用的 API>
## 系統
<相關檔案路徑,以及要參考什麼>
<不變量:哪些設計不能任意更動>
<測試放哪裡、如何驗證>
## 使用者
<誰會用、在什麼情境、最在乎什麼>
四層是檢查資訊有沒有到位的清單。短的直接貼,長的給位置;涉及 review 時,再補上範圍與版本。讓 AI 在動手前,先知道這次判斷必須依據什麼。